﻿/* Copyright (c) 2011, John Cronin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{
    class x86_64_CallConv : CallConv
    {
        public static CallConv tcdecl(Assembler.MethodToCompile mtc, StackPOV pov, Assembler ass, ThreeAddressCode call_tac)
        {
            CallConv ret = new CallConv();

            ret.CallerCleansStack = true;
            ret.CallTac = call_tac.Operator;

            /* On the x86_64 stack, arguments are 8 byte aligned
             * 
             * From the function's point of view (pov == Callee), layout is:
             * 
             * [ebp + 0]:       previous ebp
             * [ebp + 8]:       return address
             * [ebp + 16]:      argument 0
             * etc
             * 
             * assuming 8 byte arguments.  This differs from most other calling conventions in that the argument list
             * contains arguments of different lengths depending on the size of the passed value types.
             */

            ret.Arguments = new List<hardware_location>();

            int stack_pos = 0;
            int pointer_size = 8;
            hardware_location base_reg = x86_64_Assembler.Rsp;
            if (pov == CallConv.StackPOV.Callee)
            {
                stack_pos = 16;
                base_reg = x86_64_Assembler.Rbp;
            }

            Signature.Method m = null;
            if (mtc.msig is Signature.Method)
                m = mtc.msig as Signature.Method;
            else if (mtc.msig is Signature.GenericMethod)
                m = ((Signature.GenericMethod)mtc.msig).GenMethod;
            else
                throw new NotSupportedException();

            if (m.HasThis && !m.ExplicitThis)
            {
                ret.Arguments.Add(new hardware_contentsof { base_loc = base_reg, const_offset = stack_pos, size = pointer_size });
                stack_pos += pointer_size;
            }

            foreach (Signature.Param p in m.Params)
            {
                int v_size = ass.GetSizeOf(p);
                ret.Arguments.Add(new hardware_contentsof { base_loc = base_reg, const_offset = stack_pos, size = v_size });
                stack_pos += v_size;
                stack_pos = util.align(stack_pos, pointer_size);
            }

            if (call_tac.Operator == ThreeAddressCode.Op.call_void)
                ret.ReturnValue = null;
            else
            {
                var_semantic ret_vs = call_tac.GetResultSemantic();

                if (ret_vs.needs_integer)
                    ret.ReturnValue = x86_64_Assembler.Rax;
                else if (ret_vs.needs_float)
                    ret.ReturnValue = x86_64_Assembler.Xmm0;
                else
                {
                    int r_size = ass.GetSizeOf(m.RetType);
                    ret.ReturnValue = new hardware_contentsof { base_loc = base_reg, const_offset = stack_pos, size = r_size };
                    stack_pos += r_size;
                    stack_pos = util.align(stack_pos, pointer_size);
                }
            }

            ret.StackSpaceUsed = stack_pos;

            return ret;
        }
    }

    partial class x86_64_Assembler
    {
        protected override void arch_init_callconvs()
        {
            call_convs.Clear();
            call_convs.Add("cdecl", x86_64_CallConv.tcdecl);
            call_convs.Add("tcdecl", x86_64_CallConv.tcdecl);
            call_convs.Add("default", x86_64_CallConv.tcdecl);
        }

        protected CallConv callconv_conv_u4_r8
        {
            get
            {
                return MakeStaticCall("default", new Signature.Param(BaseType_Type.R8), new List<Signature.Param> { new Signature.Param(BaseType_Type.U4) }, ThreeAddressCode.Op.call_r8);
            }
        }
        protected CallConv callconv_conv_u8_r8
        {
            get
            {
                return MakeStaticCall("default", new Signature.Param(BaseType_Type.R8), new List<Signature.Param> { new Signature.Param(BaseType_Type.U8) }, ThreeAddressCode.Op.call_r8);
            }
        }
    }
}
